#       feed_utils.py
#       
#       Copyright 2008 Joshua Scotton <josh@joshuascotton.com>
#       
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
#       
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#       GNU General Public License for more details.
#       
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.

__licence__ = "GPLv2"
__copyright__ = "2008, Joshua Scotton"
__author__ = "Joshua Scotton <josh@joshuascotton.com>"

import os
import sys
import urllib
import string
import ConfigParser
from ConfigParser import ParsingError
from xml.dom import minidom 

try:
 	import pygtk
  	pygtk.require("2.4")
except:
  	pass
try:
	import gtk
  	import gtk.glade
except:
	sys.exit(1)

class FeedEntryParser:
    """Will handle encoding and html for displaying feed entries"""
    def strip_tags(self, text, valid_tags=[]):
        """
        Strips a text string of all markup tags except the tags specified
        @param text the text to be stripped
        @param valid_tags a list of the allowed tags
        """
        
        #setup variables
        valid_tag_list = []
        output = ""
        in_tag = False
        in_tag_name = False
        tag = ""
        tag_attributes = ""
        
        #append closing tags to tag list
        for next_tag in valid_tags:
            valid_tag_list.append(next_tag)
            valid_tag_list.append("/"+next_tag)
        
        #setup a list to parse from input
        parse_list = list(text)
        
        i = 0
        while i < len(parse_list):
            c = parse_list[i]
            if c == "<":
                #we are into the tag and the tag name
                in_tag = True
                in_tag_name = True
                parse_list.pop(i)
            elif in_tag:
                #we are in a tag
                if c == " " and in_tag_name:
                    #tag name has ended we are now into tag_attributes
                    in_tag_name = False
                    tag_attributes += parse_list.pop(i)
                elif c == ">":
                    #tag has finished let's clean up
                    in_tag = False
                    parse_list.pop(i)
                    #check if tag matches
                    try:
                        if len(valid_tag_list) > valid_tag_list.index(tag):
                            output += ("<" + tag + tag_attributes + ">")
                    except:
                        pass
                    tag = ""
                    tag_attributes = ""
                elif in_tag_name:
                    #we are in a tag name lets append current char to tag
                    tag += parse_list.pop(i)
                else:
                    #we are in tag attributes lets append current char to attributes
                    tag_attributes += parse_list.pop(i)
            else:
                #not in a tag so append current char to output
                output += parse_list.pop(i)
                
        return output
        
    def strip_non_pango_tags(self, text):
        """
        Strips a text string of all non-pango tags
        @param text the text to be stripped
        """
        return self.strip_tags(text, ["span","b","big","i","s","sub","sup","small","tt","u"])
        
    def convert(self, text):
        """
        This converts a text string into a format acceptable by a clutter label object
        @param text the text to be converted
        """
        
        return self.strip_non_pango_tags(text).replace('&nbsp;', ' ')
    

class OPMLParser:
    """Enables handling and parsing of opml files"""
	
    def get_rss_feeds(self, filename):
        """
        returns list of rss feeds in the opml file
        @param filename The OPML file
        @return list(String) List of RSS feed urls
        """
        list = []
        if os.path.isfile(filename):
            #this loads the xml from the opml file into xmldoc
            xmldoc = minidom.parse(filename)
        else:
            xmldoc = minidom.parse(urllib.urlopen(filename))
        #the rss feeds are in the xmlUrl attributes of the outline tags
        #we loop through all the outline nodes in the opml file and add
        #the contents of any xmlUrl attributes to the list variable
        for node in xmldoc.getElementsByTagName('outline'):
            url = node.getAttribute('xmlUrl')
            #drop any items which are .opml files or empty string
            #We do not recursively parse opml links as we could end up with an infinite loop
            if (url.strip() <> "") and ((url.strip() [-5:]) <> ".opml"):
                list.append(url)
        #returns the list of rss feeds from the opml file
        return list

    def get_liferea_opml(self, home_dir=os.path.expanduser("~")):
        """
        this returns either a empty string or the path to the current liferea opml file
        @param home_dir String defaults to ~
        @return String Path to opml file
        """
        #we'll be storing the latest liferea config folder in path and the version number, if any, in curVersion
        path = ""
        curVersion = float(0.0)
        
        #loop through home and pick out any config folders matching .liferea*
        filenames = [filename for filename in os.listdir(home_dir) if filename.startswith(".liferea")] 
        if (len(filenames) <= 0):
            #if there aren't any folders return an empty string
            return ""
        elif (len(filenames) == 1):
            #if there's only 1 path found set it as the path
            path = filenames[0]
        else:
            #in this case we have multiple posibilities
            #liferea will store the config folder as .liferea or .liferea_9.9 with 9.9 being the version number
            #all we have todo is pick the largest version number or just the .liferea folder in that older
            for fname in filenames:
                if (fname [-3:] == "rea"):
                    if (curVersion == 0.0):
                        path = fname
                elif (float(fname [-3:])> curVersion):
                    curVersion = float(fname [-3:])
                    path = fname
        
        #check if the path variable gives us a valid path and return it if it does or an empty string if it doesn't
        if os.path.isfile(home_dir + "/" + path + "/feedlist.opml"):
            return home_dir + "/" + path + "/feedlist.opml"
        else:
            return ""

class FeedConfigTools:
    """Enables Handling of Feed Config Widgets"""
    
    def add_file_feeds_to_widget(self, files, config, model, feeds, config_file='~/.entertainer/content.conf', check=True):
        """
        This changes the input values so that the feeds in the files update the other inputs
        @param files list(String)
        @param config ConfigParser
        @param model GTKModel?
        @param feeds list(String)
        @param config_file String Path defaults to ~/.entertainer/content.conf
        """
        feed_list = []
        for opml_file in files:
            #get the list of feeds from each file
            opml_feeds = OPMLParser().get_rss_feeds(opml_file)
            #add the feeds to the rss_list
            for feed in opml_feeds:
                feed_list.append(feed)
        #only check if check is set to True
        if(check):
            #check if the user really wants to add
            dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK_CANCEL, "This will add " + str(len(feed_list)) + " feeds to your feed list. Continue?")
            status = dialog.run()
            #If user has ok'd the request we can now add the feeds properly, else leave it
            if(status == gtk.RESPONSE_OK):
                #now we need append the feeds to self.feeds and content manager
                self.add_feeds_to_widget(feed_list, config, model, feeds, config_file)
            dialog.destroy()
        else:
            #now we need append the feeds to self.feeds and content manager
            self.add_feeds_to_widget(feed_list, config, model, feeds, config_file)
    
    def add_feeds_to_widget(self, feed_list, config, model, feeds, config_file='~/.entertainer/content.conf'):
        """
        This changes the input values so that the feeds in feed_list update the other inputs
        @param feed_list list(String)
        @param config ConfigParser
        @param model GTKModel?
        @param feeds list(String)
        @param config_file String Path defaults to ~/.entertainer/content.conf
        """
        try:
            for feed in feed_list:
                #drop any items which are .opml files
                #We do not recursively parse opml links as we could end up with an infinite loop
                if (feed [-5:]) <> ".opml":
                    #this is adding the feed to the content manager widget
                    model.append([feed])
                    #this adds the feed to the self.feeds ready to add to the content.conf file
                    feeds.append(feed)
            #now we can parse the new feeds and add to content.conf
            str_folders = string.join(feeds, ';')
            config.set("RSS","feeds", str_folders)
            cfg_file = file(os.path.expanduser(config_file), 'w')
            config.write(cfg_file)
        except IOError:
            #Catching IOError
            error = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, "IOError: There seems to be a problem with your selection.")
            error.run()
            error.destroy()
        
